package com.shiro.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.AuthenticatingRealm;

/**
 * 20230129  该类可以不写  以下来自AuthenticatingRealm JavaDoc
 * Realm接口的顶级抽象实现，仅实现身份验证支持（登录）操作，并将授权（访问控制）行为留给子类。
 * 身份验证缓存
 * 对于对相同帐户执行频繁重复身份验证的应用程序（例如，在 REST 或 Soap 应用程序中经常对每个请求进行身份验证），启用身份验证缓存以减轻任何后端数据源上的持续负载可能是明智的。

 * 默认情况下禁用此功能以保持与 Shiro 1.1 及更早版本的向后兼容性。它可以通过设置authenticationCachingEnabled = true来启用（当然还要用CacheManager配置 Shiro），但是注意：

 * 如果您的领域实现存在以下任一情况，则仅启用身份验证缓存：
 * doGetAuthenticationInfo实现返回AuthenticationInfo实例，其中credentials被安全地混淆并且不是明文（原始）凭证。例如，如果您的领域引用带有密码的帐户，则AuthenticationInfo的credentials已安全散列和加盐或以其他方式完全加密。
 * doGetAuthenticationInfo实现返回AuthenticationInfo实例，其中credentials是纯文本（原始）并且存储AuthenticationInfo实例的缓存区域不会溢出到磁盘并且不会通过未受保护的（非 TLS/SSL）网络传输缓存条目（可能是这种情况网络/分布式企业缓存）。即使在私人/可信/公司网络中也应该如此。

 * 这几点非常重要，因为如果启用了身份验证缓存，这个抽象类实现会将子类实现返回的 AuthenticationInfo 实例直接放入缓存中，例如：
 * cache.put(cacheKey, subclassAuthenticationInfoInstance);


 * 只有在上述两种情况适用的情况下，启用身份验证缓存才是安全的。在任何其他情况下启用都是不安全的。

 * 如果可能，请始终以安全形式（哈希+盐或加密）表示和存储凭据，以消除明文可见性。
 * 注销时身份验证缓存失效
 * 如果启用身份验证缓存，此实现将尝试在注销期间为帐户逐出（删除）缓存的身份验证数据。只有当getAuthenticationCacheKey(AuthenticationToken)和getAuthenticationCacheKey(PrincipalCollection)方法返回完全相同的值时才会发生这种情况。

 * 这些方法的默认实现期望AuthenticationToken.getPrincipal() （用户在登录期间提交的内容）和getAvailablePrincipal （领域在帐户查找后返回的内容）返回相同的确切值。例如，用户提交的用户名也是主要帐户标识符。

 * 但是，如果您的应用程序使用最终用户登录的用户名，但在身份验证后返回主键 ID 作为主要主体，那么您将需要覆盖getAuthenticationCacheKey(token)或getAuthenticationCacheKey(principals) （或两者）以确保相同的缓存键可用于任一对象。

 * 这保证了在身份验证期间用于缓存数据的相同缓存密钥（派生自AuthenticationToken ）将用于在注销期间删除缓存数据（派生自PrincipalCollection ）。
 * 不匹配的缓存键值
 * 如果getAuthenticationCacheKey(AuthenticationToken)和getAuthenticationCacheKey(PrincipalCollection)的返回值不相同，则缓存的身份验证数据删除取决于您的缓存提供程序设置。例如，缓存实现通常会根据 timeToIdle 或 timeToLive (TTL) 值逐出缓存条目。

 * 如果缓存产品的这种惰性逐出能力不够，并且您想要离散行为（强烈建议用于身份验证数据），请确保这两个方法的返回值在子类实现中是相同的
 */
public class MyAuthenticatingRealm extends AuthenticatingRealm {
  /**
   * Shiro的认证方法我们需要在这个方法中来获取用户的信息（从数据库中）
   * @param authenticationToken   用户登录时的Token（令牌），这个对象中将存放着我们用户在浏览器中存放的账号和密码
   * @return 返回一个AuthenticationInfo 对象，这个返回以后Shiro会调用这个对象中的一些方法来完成对密码的验证 密码是由Shiro进行验证是否合法
   * @throws AuthenticationException   如果认证失败，Shiro就会抛出AuthenticationException 也可以手动抛出这个AuthenticationException
   *      以及它的任意子异常类，不同的异常类型对应认证过程中的不同错误情况，我们需要根据异常类型来为用户返回特定的响应数据
   * AuthenticationException  异常的子类  可以自己抛出
   *      AccountException  账号异常  可以自己抛出
   *      UnknownAccountException  账号不存在的异常  可以自己抛出
   *      LockedAccountException   账号异常锁定异常  可以自己抛出
   *      IncorrectCredentialsException  密码错误异常（这个异常会在Shiro进行密码验证时抛出）
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    //将AuthenticationToken强转成UsernamePasswordToken 这样获取账号和密码更加的方便
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    //获取用户在浏览器中输入的账号
    String userName = token.getUsername();

    //认证账号,正常情况我们需要这里从数据库中获取账号的信息，以及其他关键数据，例如账号是否被冻结等等
    String dbUserName = userName;

    if(!"admin".equals(dbUserName) && !"zhangsan".equals(dbUserName)){//判断用户账号是否正确
      throw  new UnknownAccountException("账号错误");
    }
    if("zhangsan".equals(userName)){
      throw  new LockedAccountException("账号被锁定");
    }
    //定义一个密码(这个密码应该来自数据库)
    String dbpassword = "123456";

    /**
     * 创建密码认证对象，由Shiro自动认证密码
     * 参数1  数据库中的账号（页面账号也可）
     * 参数2  数据库中的密码
     * 参数3  当前Relam的名字
     * 如果密码认证成功，会返回一个用户身份对象；如果密码验证失败则抛出异常
     */
    //认证密码是否正确
    return new SimpleAuthenticationInfo(dbUserName,dbpassword,getName());
  }
}
